/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */
#include "stdafx.h"

#pragma once

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;
using namespace System::Runtime::ConstrainedExecution;

#include "../FastPathFinder/FastPathFinder.h"

#undef GetGValue

namespace NativeEngine
{
	public interface class IAStarNode
	{
		property int WallValue { int get(); }
		property int X { int get(); }
		property int Y { int get(); }
	};

	typedef CPathFinder* (*__stdcall PROC_PfCreate)(int* inGrid, int inSize);

	typedef void (*__stdcall PROC_PfBeginInstancing)(CPathFinder* inPathFinder, POINT* inStart, POINT* inEnd, int inContext);

	typedef double (*__stdcall PROC_PfGetGValue)(CPathFinder* inPathFinder, POINT* inForCell);

	typedef POINT* (*__stdcall PROC_PfGetResult)(CPathFinder* inPathFinder, POINT* inForCell);

	typedef POINT* (*__stdcall PROC_PfSearch)(CPathFinder* inPathFinder, POINT* inStart, POINT* inEnd, int inContext, int* outResultCount);

	typedef void (*__stdcall PROC_PfRelease)(CPathFinder* inPathFinder);

	typedef void (*__stdcall PROC_PfReleaseResult)(POINT* inResult);

	PROC_PfCreate imp_PfCreate = NULL;
	PROC_PfBeginInstancing imp_PfBeginInstancing = NULL;
	PROC_PfGetGValue imp_PfGetGValue = NULL;
	PROC_PfGetResult imp_PfGetResult = NULL;
	PROC_PfSearch imp_PfSearch = NULL;
	PROC_PfRelease imp_PfRelease = NULL;
	PROC_PfReleaseResult imp_PfReleaseResult = NULL;

	generic<class TPathNode> where TPathNode : IAStarNode
		public ref class AStarSolver : CriticalFinalizerObject, IDisposable
	{
	private:
		static bool m_IsInitialized = false;
		cli::array<TPathNode, 2>^ m_SearchSpace;
		cli::array<int>^ m_InternalGrid;
		int m_Width, m_Height, m_InternalSize;
		GCHandle^ m_hInternalGrid;
		CPathFinder* m_PathFinder;
		int m_SizeShift;

		static void Initialize()
		{
			if(m_IsInitialized)
				return;

			HMODULE hThis = LoadLibraryA("FastPathFinder32.dll");

			imp_PfCreate = (PROC_PfCreate)GetProcAddress(hThis, "_PfCreate@8");
			imp_PfBeginInstancing = (PROC_PfBeginInstancing)GetProcAddress(hThis, "_PfBeginInstancing@16");
			imp_PfGetGValue = (PROC_PfGetGValue)GetProcAddress(hThis, "_PfGetGValue@8");
			//imp_PfGetResult = (PROC_PfGetResult)GetProcAddress(hThis, "_PfGetResult");
			imp_PfSearch = (PROC_PfSearch)GetProcAddress(hThis, "_PfSearch@20");
			imp_PfRelease = (PROC_PfRelease)GetProcAddress(hThis, "_PfRelease@4");
			imp_PfReleaseResult = (PROC_PfReleaseResult)GetProcAddress(hThis, "_PfReleaseResult@4");

			if((imp_PfCreate == NULL) || (imp_PfBeginInstancing == NULL) || (imp_PfGetGValue == NULL) ||
					(imp_PfSearch == NULL) || (imp_PfRelease == NULL) || (imp_PfReleaseResult == NULL))
				throw gcnew System::MissingMethodException();

			m_IsInitialized = true;
		}

	public:

		property cli::array<TPathNode, 2>^ SearchSpace { cli::array<TPathNode, 2>^ get() { return m_SearchSpace; } }
		property int Width { int get() { return m_Width; } }
		property int Height { int get() { return m_Height; } }

		bool IsWalkable(System::Drawing::Point inCell, int inMovableType)
		{
			return m_InternalGrid[inCell.X + (inCell.Y << m_SizeShift)] <= inMovableType;
		}

		AStarSolver(cli::array<TPathNode, 2>^ inGrid)
		{
			Initialize();

			m_SearchSpace = inGrid; 
            m_Width = inGrid.GetLength(0);
            m_Height = inGrid.GetLength(1);

			// compute power of two
            int maxEdge = Math::Max(Width, Height);

            m_InternalSize = maxEdge;
            m_InternalSize = (int)Math::Log(m_InternalSize, 2);

            if(Math::Pow(2, m_InternalSize) < maxEdge)
                m_InternalSize = (int)Math::Pow(2, m_InternalSize + 1);
            else
                m_InternalSize = (int)Math::Pow(2, m_InternalSize);

            if(m_InternalSize < maxEdge)
                throw gcnew ApplicationException();

			m_SizeShift = (int)Math::Log(m_InternalSize, 2);

			// setup native solver
            m_InternalGrid = gcnew cli::array<int>(m_InternalSize * m_InternalSize);
            m_hInternalGrid = GCHandle::Alloc(m_InternalGrid, GCHandleType::Pinned);

            for (int x = 0, offset = 0; x < m_InternalSize; x++)
            {
                for (int y = 0; y < m_InternalSize; y++)
                {
                    if ((x >= Width) || (y >= Height))
                        offset++;
                    else
                        m_InternalGrid[offset++] = inGrid[x, y]->WallValue;
                }
            }

			m_PathFinder = imp_PfCreate((int*)m_hInternalGrid->AddrOfPinnedObject().ToPointer(), m_InternalSize);
		}

		!AStarSolver()
		{
			this->~AStarSolver();
		}

		~AStarSolver()
		{
			if (m_PathFinder != NULL)
                imp_PfRelease(m_PathFinder);

            m_PathFinder = NULL;

            if ((m_hInternalGrid != nullptr) && (m_hInternalGrid->IsAllocated))
                m_hInternalGrid->Free();

            m_hInternalGrid = nullptr;
            m_InternalGrid = nullptr;
            m_SearchSpace = nullptr;
		}

		LinkedList<TPathNode>^ Search(System::Drawing::Point inStartNode, System::Drawing::Point inEndNode, int inMovableValue)
		{
			LinkedList<TPathNode>^ result = gcnew LinkedList<TPathNode>();
			POINT* nativeResult = NULL;
			int resultCount = 0;

			try
			{
				POINT startNode, endNode;

				startNode.x = inStartNode.X;
				startNode.y = inStartNode.Y;

				endNode.x = inEndNode.X;
				endNode.y = inEndNode.Y;

				nativeResult = imp_PfSearch(m_PathFinder, &startNode, &endNode, inMovableValue, &resultCount);

				for(int i = 0; i < resultCount; i++)
				{
					result->AddLast(SearchSpace[nativeResult[i].x, nativeResult[i].y]);
				}
			}
			finally
			{
				if(nativeResult != NULL)
					imp_PfReleaseResult(nativeResult);
			}

			return result;
		}

		void BeginInstancing(System::Drawing::Point inStartNode, System::Drawing::Point inEndNode, int inMovableValue)
		{
			POINT startNode, endNode;

			startNode.x = inStartNode.X;
			startNode.y = inStartNode.Y;

			endNode.x = inEndNode.X;
			endNode.y = inEndNode.Y;

			imp_PfBeginInstancing(m_PathFinder, &startNode, &endNode, inMovableValue);
		}

		double GetGValue(System::Drawing::Point inForCell)
		{
			POINT forCell;

			forCell.x = inForCell.X;
			forCell.y = inForCell.Y;

			return imp_PfGetGValue(m_PathFinder, &forCell);
		}
	};
}